# frozen_string_literal: true

class KlotskiViewModel
  class MovePath
    def initialize
      @prev_path = ArrayObservable.new.tap do |o|
        o.on_event {dispatch_event(:can_recede, can_recede?)}
      end

      @next_path = ArrayObservable.new.tap do |o|
        o.on_event {dispatch_event(:can_advance, can_advance?)}
      end
    end

    def reset(initial_move)
      @prev_path.replace([initial_move])
      @next_path.clear
      dispatch_event(:board_reset, initial_move.board)
    end

    def <<(move)
      @prev_path << move
      @next_path.clear
      dispatch_event(:moved, move)
    end

    def can_advance?
      return @next_path.any?
    end

    def advance
      return nil unless can_advance?

      @prev_path << @next_path.shift
      return @prev_path.last.tap do |move|
        dispatch_event(:moved, move)
      end
    end

    def can_recede?
      @prev_path.any? && !@prev_path.last.initial?
    end

    def recede
      return nil unless can_recede?

      @next_path.unshift(@prev_path.pop)
      return @next_path.first.reverse.tap do |move|
        dispatch_event(:moved, move)
      end
    end

    def current_board
      @prev_path.last.board
    end

    def add_solution(solution)
      @next_path.set(solution&.any? ? solution.drop(1) : [])
    end

    def dispatch_event(method, *args)
      @observable&.dispatch_event(method, *args)
    end

    def observable(&block)
      @observable ||= Class.new(Observable) do
        def initialize(move_path)
          super()
          @move_path = move_path
        end

        def [](name)
          case name
          when :current_board then @move_path.current_board
          else super
          end
        end
      end.new(self)

      block&.call(@observable)
      return @observable
    end
  end
end
